package com.virtual.repository.utils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang.reflect.FieldUtils;

/**
 * Class containing methods connected with reflections mechanism.
 * @author kchaber
 *
 */
public class ReflectionUtil {
	
	private static final String BEAN_UTILS_FIELD_SEPARATOR = ".";
	
	/**
	 * Returns a <code>Field</code> object that reflects the specified declared field of the class given by <code>queryClass</code>. 
	 * The name parameter given by <code>property</code> is a String that specifies the simple name of the desired field.
	 * Method searches recursively to the last superclass.
	 * 
	 * @param property the given property name.
	 * @return <code>Field</code> if the <code>property</code> is the class member.
	 * @throws NoSuchFieldException if the <code>property</code> is not the class member.
	 */
	public static Field getDeclaredField(Class<?> clazz, String property) throws NoSuchFieldException {
		Field result = FieldUtils.getField(clazz, property, true);

		if (result == null) {
			throw new NoSuchFieldException();
		}
		
		return result;
	}
	
	/**
	 * Sets the field with name given by <code>field</code> in the given <code>target</code> object 
	 * with the given <code>value</value>
	 * @param target the target object, which contains the field with name given by <code>fieldName</code>
	 * @param fieldName the name of the field to write to
	 * @param value the value to set for the field
	 * @throws IllegalStateException
	 */
	public static void setField(Object target, String fieldName, String value) {
		try {
			PropertyDescriptor pd = new PropertyDescriptor(fieldName, target.getClass());
			Method setter = pd.getWriteMethod();
			Class<?> setterType = setter.getParameterTypes()[0];

			Object setValue = value;
			if (setterType.isAssignableFrom(Integer.class) || setterType.isAssignableFrom(int.class)) {
				setValue = Integer.valueOf(value);
			}

			setter.invoke(target, setValue);

		} catch (Exception e) {
			throw new IllegalStateException("Cannot set the field: " + fieldName + " in class: " + target.getClass() + " with value: " + value + ". Check getters and setters");
		}
	}
	
	/**
	 * Sets the field with name given by <code>field</code> in the given <code>target</code> object 
	 * with the given <code>value</value>.
	 * @param target the target object, which contains the field with name given by <code>fieldName</code>
	 * @param fieldName the name of the field to write to
	 * @param value the value to set for the field
	 * @throws IllegalStateException
	 */
	public static void setField(Object target, String fieldName, Object value) {
		try {
			
			PropertyUtilsBean propUtils = new PropertyUtilsBean();
			propUtils.setNestedProperty(target, fieldName, value);

		} catch (Exception e) {
			throw new IllegalStateException("Cannot set the field: " + fieldName + " in class: " + target.getClass() + " with value: " + value + ". Check getters and setters");
		}
	}
	
	/**
	 * Sets the field with name given by <code>field</code> in the given <code>target</code> object 
	 * with the given <code>value</value>.
	 * @param target the target object, which contains the field with name given by <code>fieldName</code>
	 * @param fieldName the name of the field to write to - can be separated by given separator, 
	 * 		  then it represents field's name of the nested object
	 * @param value the value to set for the field
	 * @param separator the separator for the nested objects
	 */
	public static void setField(Object target, String fieldName, Object value, String separator) {
		try {
			String parsedFieldName = fieldName.replaceAll(separator, BEAN_UTILS_FIELD_SEPARATOR);
			PropertyUtilsBean propUtils = new PropertyUtilsBean();
			propUtils.setNestedProperty(target, parsedFieldName, value);
		} catch (Exception e) {
			throw new IllegalStateException("Cannot set the field: " + fieldName + " in class: " + target.getClass() + " with value: " + value + ". Check getters and setters");
		}
	}
	
	public static void copyFields(Object target, Object other) {
		try {
			Field[] fields = other.getClass().getDeclaredFields();
			for (Field field : fields) {
				field.setAccessible(true);
				Object fieldValue = field.get(other);
				if (fieldValue != null) {
					BeanUtils.copyProperty(target, field.getName(), field.get(other));
				}
			}
		} catch (Exception e) {
			throw new IllegalStateException("Cannot copy fields from object: " + other + " to object: " + target + " due to: " + e);
		}
	}
}
